﻿// --------------------------------------------------------------------------------------------------------------------
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
// <credits>
// Kevin Marshall http://blogs.claritycon.com/blog/2010/10/13/wp7-page-transitions-sample/
// </credits>
// --------------------------------------------------------------------------------------------------------------------


namespace WP7Contrib.View.Transitions.Extensions
{
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Navigation;

    using Microsoft.Phone.Controls;
    using Microsoft.Phone.Shell;

    using WP7Contrib.View.Controls.Extensions;
    using WP7Contrib.View.Transitions.Animation;

    /// <summary>
    /// The dialog service.
    /// </summary>
    public class DialogService
    {
        #region Constants and Fields

        /// <summary>
        /// The swivel in storyboard.
        /// </summary>
        internal static readonly string SwivelInStoryboard =
            @"<Storyboard xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
            <DoubleAnimation BeginTime=""0:0:0"" Duration=""0"" 
                                Storyboard.TargetProperty=""(UIElement.Projection).(PlaneProjection.CenterOfRotationY)"" 
                                Storyboard.TargetName=""LayoutRoot""
                                To="".5""/>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.Projection).(PlaneProjection.RotationX)"" Storyboard.TargetName=""LayoutRoot"">
                <EasingDoubleKeyFrame KeyTime=""0"" Value=""-30""/>
                <EasingDoubleKeyFrame KeyTime=""0:0:0.35"" Value=""0"">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <ExponentialEase EasingMode=""EaseOut"" Exponent=""6""/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.Opacity)""
                                            Storyboard.TargetName=""LayoutRoot"">
                <DiscreteDoubleKeyFrame KeyTime=""0"" Value=""1"" />
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>";

        /// <summary>
        /// The swivel out storyboard.
        /// </summary>
        internal static readonly string SwivelOutStoryboard =
            @"<Storyboard xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
            <DoubleAnimation BeginTime=""0:0:0"" Duration=""0"" 
                                Storyboard.TargetProperty=""(UIElement.Projection).(PlaneProjection.CenterOfRotationY)"" 
                                Storyboard.TargetName=""LayoutRoot""
                                To="".5""/>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.Projection).(PlaneProjection.RotationX)"" Storyboard.TargetName=""LayoutRoot"">
                <EasingDoubleKeyFrame KeyTime=""0"" Value=""0""/>
                <EasingDoubleKeyFrame KeyTime=""0:0:0.25"" Value=""45"">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <ExponentialEase EasingMode=""EaseIn"" Exponent=""6""/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.Opacity)""
                                            Storyboard.TargetName=""LayoutRoot"">
                <DiscreteDoubleKeyFrame KeyTime=""0"" Value=""1"" />
                <DiscreteDoubleKeyFrame KeyTime=""0:0:0.267"" Value=""0"" />
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>";

        /// <summary>
        /// The slide down storyboard.
        /// </summary>
        internal static readonly string SlideDownStoryboard =
            @"
        <Storyboard  xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(TranslateTransform.Y)"" 
                                           Storyboard.TargetName=""LayoutRoot"">
                <EasingDoubleKeyFrame KeyTime=""0"" Value=""0""/>
                <EasingDoubleKeyFrame KeyTime=""0:0:0.25"" Value=""150"">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <ExponentialEase EasingMode=""EaseIn"" Exponent=""6""/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimation Storyboard.TargetProperty=""(UIElement.Opacity)"" From=""1"" To=""0"" Duration=""0:0:0.25"" 
                                 Storyboard.TargetName=""LayoutRoot"">
                <DoubleAnimation.EasingFunction>
                    <ExponentialEase EasingMode=""EaseIn"" Exponent=""6""/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>
        </Storyboard>";

        /// <summary>
        /// The slide up storyboard.
        /// </summary>
        internal static readonly string SlideUpStoryboard =
            @"
        <Storyboard  xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(TranslateTransform.Y)"" 
                                           Storyboard.TargetName=""LayoutRoot"">
                    <EasingDoubleKeyFrame KeyTime=""0"" Value=""150""/>
                    <EasingDoubleKeyFrame KeyTime=""0:0:0.35"" Value=""0"">
                        <EasingDoubleKeyFrame.EasingFunction>
                            <ExponentialEase EasingMode=""EaseOut"" Exponent=""6""/>
                        </EasingDoubleKeyFrame.EasingFunction>
                    </EasingDoubleKeyFrame>
                </DoubleAnimationUsingKeyFrames>
            <DoubleAnimation Storyboard.TargetProperty=""(UIElement.Opacity)"" From=""0"" To=""1"" Duration=""0:0:0.350"" 
                                 Storyboard.TargetName=""LayoutRoot"">
                <DoubleAnimation.EasingFunction>
                    <ExponentialEase EasingMode=""EaseOut"" Exponent=""6""/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>
        </Storyboard>";

        /// <summary>
        /// The popup container.
        /// </summary>
        private static ContentPresenter popupContainer;

        /// <summary>
        /// The hide storyboard.
        /// </summary>
        private Storyboard hideStoryboard;

        /// <summary>
        /// The original app bar.
        /// </summary>
        private IApplicationBar originalAppBar;

        /// <summary>
        /// The overlay.
        /// </summary>
        private Panel overlay;

        /// <summary>
        /// The page.
        /// </summary>
        private PhoneApplicationPage page;

        /// <summary>
        /// The root visual.
        /// </summary>
        private PhoneApplicationFrame rootVisual;

        /// <summary>
        /// The show storyboard.
        /// </summary>
        private Storyboard showStoryboard;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DialogService"/> class.
        /// </summary>
        public DialogService()
        {
            this.AnimationType = AnimationTypes.Slide;
        }

        #endregion

        #region Events

        /// <summary>
        /// The closed.
        /// </summary>
        public event EventHandler Closed;

        /// <summary>
        /// The opened.
        /// </summary>
        public event EventHandler Opened;

        #endregion

        #region Enums

        /// <summary>
        /// The animation types.
        /// </summary>
        public enum AnimationTypes
        {
            /// <summary>
            /// The slide.
            /// </summary>
            Slide, 

            /// <summary>
            /// The swivel.
            /// </summary>
            Swivel
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets AnimationType.
        /// </summary>
        public AnimationTypes AnimationType { get; set; }

        /// <summary>
        /// Gets or sets BackgroundBrush.
        /// </summary>
        public Brush BackgroundBrush { get; set; }

        /// <summary>
        /// Gets or sets Child.
        /// </summary>
        public FrameworkElement Child { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether HasPopup.
        /// </summary>
        public bool HasPopup { get; set; }

        /// <summary>
        /// Gets or sets VerticalOffset.
        /// </summary>
        public double VerticalOffset { get; set; }

        /// <summary>
        /// Gets PopupContainer.
        /// </summary>
        internal static ContentPresenter PopupContainer
        {
            get
            {
                if (popupContainer == null)
                {
                    var rootVisual = Application.Current.RootVisual as PhoneApplicationFrame;
                    popupContainer =
                        Enumerable.FirstOrDefault<ContentPresenter>(rootVisual.GetVisualDescendants().OfType<ContentPresenter>().Where(
                                p => p.Name.Equals("FauxPopup")));
                }

                return popupContainer;
            }
        }

        /// <summary>
        /// Gets or sets AppBar.
        /// </summary>
        internal ApplicationBar AppBar { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether IsOpen.
        /// </summary>
        public bool IsOpen { get; set; }

        /// <summary>
        /// Gets RootVisual.
        /// </summary>
        internal FrameworkElement RootVisual
        {
            get
            {
                if (this.rootVisual == null)
                {
                    this.rootVisual = Application.Current.RootVisual as PhoneApplicationFrame;
                    this.page = Enumerable.FirstOrDefault<PhoneApplicationPage>(this.rootVisual.GetVisualDescendants().OfType<PhoneApplicationPage>());
                }

                return this.rootVisual;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The hide.
        /// </summary>
        public void Hide()
        {
            if (!this.IsOpen)
            {
                return;
            }

            if (this.page != null)
            {
                this.page.BackKeyPress -= this.OnBackKeyPress;
                this.page.NavigationService.Navigated -= this.OnNavigated;

                var animatedBasePage = this.page as AnimatedBasePage;
                if (animatedBasePage != null && this.originalAppBar != null)
                {
                    this.page.ApplicationBar = this.originalAppBar;
                }
                else
                {
                    this.page.ApplicationBar = null;
                }

                this.page = null;
            }

            this.hideStoryboard.Stop();
            foreach (var t in this.hideStoryboard.Children)
            {
                Storyboard.SetTarget(t, this.overlay);
            }

            this.hideStoryboard.Completed += this.HideStoryboardCompleted;
            this.hideStoryboard.Begin();
        }

        /// <summary>
        /// The on back key press.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        public void OnBackKeyPress(object sender, CancelEventArgs e)
        {
            if (this.HasPopup)
            {
                e.Cancel = true;
                return;
            }

            if (!this.IsOpen)
            {
                return;
            }

            e.Cancel = true;
            this.Hide();
        }

        /// <summary>
        /// Shows the context menu.
        /// </summary>
        public void Show()
        {
            this.IsOpen = true;

            this.InitializePopup();

            this.overlay.Opacity = 0;

            this.page.BackKeyPress += this.OnBackKeyPress;
            this.page.NavigationService.Navigated += this.OnNavigated;

            this.originalAppBar = this.page.ApplicationBar;

            this.showStoryboard.Completed += this.ShowStoryboardCompleted;
            foreach (Timeline t in this.showStoryboard.Children)
            {
                Storyboard.SetTarget(t, this.overlay);
            }

            this.overlay.InvokeOnLayoutUpdated(
                () => Deployment.Current.Dispatcher.BeginInvoke(
                    () =>
                        {
                            this.showStoryboard.Begin();

                            if (this.page != null)
                            {
                                this.page.ApplicationBar = this.AppBar;
                            }
                        }));

            // Show the popup
            PopupContainer.Visibility = Visibility.Visible;
        }

        #endregion

        #region Methods

        /// <summary>
        /// The initialize popup.
        /// </summary>
        private void InitializePopup()
        {
            // Add overlay which is the size of RootVisual
            this.overlay = new Grid { Width = this.RootVisual.ActualWidth, Height = this.RootVisual.ActualHeight };

            switch (this.AnimationType)
            {
                case AnimationTypes.Slide:
                    this.showStoryboard = XamlReader.Load(SlideUpStoryboard) as Storyboard;
                    this.hideStoryboard = XamlReader.Load(SlideDownStoryboard) as Storyboard;
                    this.overlay.RenderTransform = new TranslateTransform();
                    break;

                default:
                    this.showStoryboard = XamlReader.Load(SwivelInStoryboard) as Storyboard;
                    this.hideStoryboard = XamlReader.Load(SwivelOutStoryboard) as Storyboard;
                    this.overlay.Projection = new PlaneProjection();
                    break;
            }

            this.overlay.Children.Add(this.Child);

            this.Child.Width = this.rootVisual.ActualWidth;

            // Initialize popup to draw the context menu over all controls
            PopupContainer.Content = this.overlay;

            if (this.BackgroundBrush != null)
            {
                this.overlay.Background = this.BackgroundBrush;
            }

            this.overlay.Margin = new Thickness(0, this.VerticalOffset, 0, 0);
        }

        /// <summary>
        /// The on navigated.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnNavigated(object sender, NavigationEventArgs e)
        {
            this.Hide();
        }

        /// <summary>
        /// The _hide storyboard_ completed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void HideStoryboardCompleted(object sender, EventArgs e)
        {
            this.hideStoryboard.Completed -= this.HideStoryboardCompleted;
            this.hideStoryboard = null;

            this.IsOpen = false;

            if (PopupContainer != null)
            {
                PopupContainer.Visibility = Visibility.Collapsed;
                PopupContainer.Content = null;
            }

            if (null != this.overlay)
            {
                this.overlay.Children.Clear();
                this.overlay = null;
            }

            if (this.Closed != null)
            {
                this.Closed(this, null);
            }
        }

        /// <summary>
        /// The _show storyboard_ completed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ShowStoryboardCompleted(object sender, EventArgs e)
        {
            var sb = sender as Storyboard;
            if (sb != null)
            {
                sb.Completed -= this.ShowStoryboardCompleted;
            }

            if (this.Opened != null)
            {
                this.Opened(this, null);
            }
        }

        #endregion
    }
}